package com.acme.bankapp.service.bank;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

import com.acme.bankapp.domain.account.Account;
import com.acme.bankapp.domain.account.CheckingAccount;
import com.acme.bankapp.domain.account.SavingAccount;
import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.client.Client;
import com.acme.bankapp.domain.client.Gender;
import com.acme.bankapp.exception.ClientExistsException;
import com.acme.bankapp.exception.DeserializationException;

public class BankDataLoaderService {

    private static class ParsedClientString {
	public final String accountBalance;
	public final String accountOverdraft;
	public final String accountType;
	public final String clientGender;
	public final String clientName;

	public ParsedClientString(final String accountType,
		final String accountBalance, final String accountOverdraft,
		final String clientName, final String clientGender) {
	    super();
	    this.accountType = accountType;
	    this.accountBalance = accountBalance;
	    this.accountOverdraft = accountOverdraft;
	    this.clientName = clientName;
	    this.clientGender = clientGender;
	}
    }

    private static final String keyValuePairPattern = "(accounttype|balance|overdraft|name|gender)=[\\w\\d\\.]+";
    private static final String FeedLinePattern = "accounttype=(c|s);"
	    + "balance=(\\d+|\\d+\\.\\d+);"
	    + "(overdraft=(\\d+|\\d+\\.\\d+);){0,1}" + "name=\\w+;"
	    + "gender=(m|f);$";

    public static BankService load(final File file) throws IOException,
	    DeserializationException {
	Bank bank = new Bank();
	BankService bankService = new BankService(bank);

	if (!file.exists()) {
	    throw new IOException("File " + file.getPath()
		    + " does not exists!");
	}
	readClients(file, bankService);
	return bankService;
    }

    public static BankService load(final String fileName) throws IOException,
	    DeserializationException {
	return load(new File(fileName));
    }

    public static Client makeClientFromString(final String input)
	    throws DeserializationException {
	if (input != null && input.matches(FeedLinePattern)) {
	    ParsedClientString parsedinput = parseClientString(input);
	    return makeClient(parsedinput);
	}
	return null;
    }

    private static Client makeClient(final ParsedClientString clientStr)
	    throws DeserializationException {
	Account account = makeAccount(clientStr);
	Gender clientGender = null;
	if (clientStr.clientGender.equals(BankDataFormat.GenderValueLexem.MALE
		.toString())) {
	    clientGender = Gender.MALE;
	} else if (clientStr.clientGender
		.equals(BankDataFormat.GenderValueLexem.FEMALE.toString())) {
	    clientGender = Gender.FEMALE;
	} else {
	    throw new DeserializationException("Bad value of gender: "
		    + clientStr.clientGender);
	}
	Client client = new Client(clientStr.clientName, clientGender, account);
	return client;
    }

    private static ParsedClientString parseClientString(final String input) {
	if (input == null) {
	    throw new NullPointerException();
	}
	Scanner scanner = new Scanner(input);
	scanner.useDelimiter(BankDataFormat.PAIRS_DELIMER);
	String accountBalance = null;
	String accountOverdraft = null;
	String accountType = null;
	String clientGender = null;
	String clientName = null;

	while (scanner.hasNext(keyValuePairPattern)) {
	    String[] pair = scanner.next(keyValuePairPattern).split(
		    BankDataFormat.KEY_VALUE_DELIMER);
	    if (pair[0]
		    .equals(BankDataFormat.KeysLexem.ACCOUNT_TYPE.toString())) {
		accountType = pair[1];
	    } else if (pair[0].equals(BankDataFormat.KeysLexem.ACCOUNT_BALANCE
		    .toString())) {
		accountBalance = pair[1];
	    } else if (pair[0]
		    .equals(BankDataFormat.KeysLexem.ACCOUNT_OVERDRAFT
			    .toString())) {
		accountOverdraft = pair[1];
	    } else if (pair[0].equals(BankDataFormat.KeysLexem.CLIENT_GENDER
		    .toString())) {
		clientGender = pair[1];
	    } else if (pair[0].equals(BankDataFormat.KeysLexem.CLIENT_NAME
		    .toString())) {
		clientName = pair[1];
	    } else {
		continue;
	    }
	}
	ParsedClientString parsedinput = null;
	if (accountType != null && accountBalance != null && clientName != null
		&& clientGender != null) {
	    parsedinput = new ParsedClientString(accountType, accountBalance,
		    accountOverdraft, clientName, clientGender);
	}
	scanner.close();
	return parsedinput;
    }

    private static double stringToDouble(final String str) {
	return (str == null ? 0.0 : Double.valueOf(str));
    }

    protected static Account makeAccount(final ParsedClientString clientStr)
	    throws DeserializationException {
	Account account = null;
	double accountBalance = stringToDouble(clientStr.accountBalance);
	double accountOverdraft = stringToDouble(clientStr.accountOverdraft);
	if (clientStr.accountType
		.equals(BankDataFormat.AccountTypeValueLexem.SAVING.toString())) {
	    account = new SavingAccount(accountBalance);
	} else if (clientStr.accountType
		.equals(BankDataFormat.AccountTypeValueLexem.CHECKING
			.toString())) {
	    account = new CheckingAccount(accountBalance, accountOverdraft);
	} else {
	    throw new DeserializationException(
		    "Unknown account type "
			    + clientStr.accountType
				    .equals(BankDataFormat.AccountTypeValueLexem.CHECKING
					    .toString()));
	}
	return account;
    }

    protected static void readClients(final File file,
	    final BankService bankService) throws IOException,
	    DeserializationException {
	Scanner scanner = new Scanner(file);
	while (scanner.hasNextLine()) {
	    String input = scanner.nextLine();
	    Client client = makeClientFromString(input);
	    if (client == null) {
		continue;
	    }
	    try {
		bankService.addClient(client);
	    } catch (ClientExistsException e) {
		System.err.println("Dublicate found: " + client);
	    }
	}
	scanner.close();
    }
}
